#!/usr/bin/env python
# -*- coding: utf-8 -*-
import json
import os
import sys
import time
import traceback
from copy import deepcopy
from datetime import datetime

sys.path.append('/mnt/moonbrook/Moonbrook')
sys.path.append('/mnt/moonbrook/Moonbrook/datastore')
sys.path.append('/mnt/moonbrook/Moonbrook/configs')
sys.path.insert(0, '/mnt/moonbrook/Moonbrook/model')
import psutil
from datastore.mongodb import MongoDBClientWrapper

from models import MongoQueryConfig
from utils import Utils
from pymongo import MongoClient
from configs import config_obj

reload(sys)
sys.setdefaultencoding('utf-8')


class ExportAndTjBase(object):
    '''统计、导出任务实现 的基类'''

    def __init__(self, file_name):
        self.db = MongoDBClientWrapper()
        # 脚本开始时间
        self.start_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        self.file_name = file_name
        self.init_file(file_name)
        self.log('***  the start', suffix=' ***')
        self.contact_col = self.db.p_col('contact')
        self.ent_col = self.db.p_col('enterprise')
        # silk
        silk_config = config_obj['silk_mongodb']
        silk_client = MongoClient(silk_config['host'], connect=False)
        self.silk_db = silk_client[silk_config['database']]
        self.silk_db.authenticate(silk_config['user'], silk_config['pwd'])

    def init_file(self, abs_file_name, mod='w'):
        '''
        初始化需要写入的文件
        @param abs_file_name: 绝对路径文件名  str
        @param mod: 文件读写模式  str
        @return: None
        '''
        dir_path = os.path.dirname(abs_file_name)
        if not os.path.exists(dir_path):
            os.makedirs(dir_path)
        self.file_dir_path = dir_path

        self.write_log_f = open('{}_log'.format(abs_file_name), mod)  # 运行日志
        self.write_dc_f = open('{}_dc'.format(abs_file_name), mod)  # 导出到文件
        self.write_tj_f = open('{}_tj'.format(abs_file_name), mod)  # 统计信息
        self.write_robot_f = open('{}_robot'.format(abs_file_name), mod)  # 机器人日志
        self.write_error_f = open('{}_error'.format(abs_file_name), mod)  # 异常日志

    def close_file(self):
        '''
        关闭所有文件流
        @return: None
        '''
        self.log('\n***  the end ', suffix='***')
        self.write_log_f.close()
        self.write_dc_f.close()
        self.write_tj_f.close()
        self.write_robot_f.close()
        self.write_error_f.close()

    def is_data_legal(self, must_exists_values, must_has_one_exists_values):
        '''
        检查数据的合法性，合法返回True
        @param must_exists_values: 这些字段值都必须是非空值，否则返回False
        @param must_has_one_exists_values: 这些字段值必须最少有一个是非空值，否则返回False
        @return:
        '''
        is_legal = True
        if self.is_values_has_none(must_exists_values):
            self.write_error_f.write('not all exists: {}\n'.format(must_exists_values))
            is_legal = False
        elif self.is_values_all_none(must_has_one_exists_values):
            self.write_error_f.write('not any exists {}\n'.format(must_has_one_exists_values))
            is_legal = False
        return is_legal

    def is_values_has_none(self, values):
        '''
        如果values有元素，则判断values中的所有元素 是否存在空值(None, '',[],{})，存在则返回True、
        否则返回False
        @param values:非空list
        @return:bool
        '''
        has_none_flag = False  # 最少一个为空
        for value in values:
            if value in [None, '', [], {}, ()]:
                has_none_flag = True
                break

        return has_none_flag

    def is_values_all_none(self, values):
        '''
        如果values有元素，判断values中的所有元素 是否都为空，否则返回False
        @param values:非空list
        @return:bool
        '''
        all_none_flag = False  # 如果values无元素，则不能表示全为空
        if values:
            all_none_flag = True  # 全为空
            for value in values:
                if value not in [None, '', [], {}, ()]:
                    all_none_flag = False
                    break

        return all_none_flag

    def memory_check(self, max_percent=98):
        '''
        内存检测,超过指定阈值则sleep5分钟
        @param max_percent: int
        @return: None
        '''
        memory_info = psutil.virtual_memory()
        memory_percent = memory_info.percent
        while memory_percent > max_percent:  # 内存占比
            self.write_log_f.write('内存情况:{}\tnow:{}\n'.format(memory_percent, datetime.now()))
            self.write_log_f.flush()
            time.sleep(60 * 5)
            memory_info = psutil.virtual_memory()
            memory_percent = memory_info.percent
        return memory_percent

    def log(self, log_info='', suffix=''):
        '''
        记录当前日志信息
        @param log_info: 要记录的日志信息
        @param suffix: 单行日志尾部信息
        @return: None
        '''
        memory_percent = self.memory_check()
        current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        log_info += '\tmemory_percent：{}\tnow：{} {}\n'.format(memory_percent, current_time, suffix)
        self.write_log_f.write(log_info)
        # 让开发者立即看到脚本效果
        self.write_log_f.flush()

    def get_entName_by_nameId(self, nameId, is_need_decrypt=True):
        '''
        通过nameId去企业表得到解密企业名
        @param nameId: str
        @param is_need_decrypt: bool
        @return: ent_name
        '''
        dic = self.ent_col.find_one({'_id': nameId}, projection=['name'])
        if isinstance(dic, dict):
            ent_dic = Utils.decrypt_one_record(dic, need_strf_time=True, need_decrypt=True)
            name = ent_dic.get('name')
            return name

    def handle_index_log(self, index, save_result_dic,save_every_times=1000):
        '''
        根据索引打日志
        @param index: 当前下标
        @param save_result_dic: 当前统计结果
        @return: None
        '''
        if index == 0:
            self.log('find is ok!')
        if index % 10000 == 0:
            if index % save_every_times == 0:
                self.save_tj_info(save_result_dic)
            self.log('index：{}'.format(index))

    def save_tj_info(self, result_dic):
        '''
        保持统计信息到统计文件
        @param result_dic: 统计信息 dict
        @return: None
        '''
        result_dic = deepcopy(result_dic)
        current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        if isinstance(result_dic, dict):
            dic_result = Utils.serializable_bloom(result_dic)
            json_str_result = json.dumps(dic_result, indent=4, ensure_ascii=False)
            self.write_tj_f.write(
                'save at {}\n{}\n\n\n'.format(current_time, json_str_result))
        else:
            self.write_tj_f.write(
                'save_result_dic is not dict： {}\n{}\n\n\n'.format(current_time, type(result_dic)))
        self.write_tj_f.flush()

    def get_records_crusor(self, config,is_silk=False):
        '''
        通过config，获得查询结果cursor对象
        @param config: config = {
            'col_name': 集合名称 str 必要
            'filter': 筛选条件 dic  必要
            'projection': 投影 list of str  非必要
            'limit': int  非必要
            'skip': int  非必要 ,用于断点续导，从最老-最新
        @return: cursor对象
        '''
        if is_silk:
            col = self.silk_db.get_collection(config[MongoQueryConfig.col_name])
        else:
            col = self.db.p_col(config[MongoQueryConfig.col_name])
        filter = config[MongoQueryConfig.filter]
        projection = config.get(MongoQueryConfig.projection)
        params = {
            MongoQueryConfig.filter: filter,
            "no_cursor_timeout" :True
        }
        if projection is not None:
            params.update(projection=projection)
        # 0 mobile 手机 ； 1 phone 固话 ；2 email
        if config.get(MongoQueryConfig.limit):
            if config.get(MongoQueryConfig.skip):
                # 从最老-最新，跳过skip返回limit条数据
                # 直接find是最老-最新   "$natural", -1是最新-最老
                # records_crusor = col.find(**params).limit(config[MongoQueryConfig.limit]).sort([("$natural", -1)])
                records_crusor = col.find(**params).skip(config[MongoQueryConfig.skip]).limit(
                    config[MongoQueryConfig.limit])
            else:
                records_crusor = col.find(**params).limit(config[MongoQueryConfig.limit])
        else:
            if config.get(MongoQueryConfig.skip):
                # 从最老-最新，跳过skip返回剩余所有数据
                records_crusor = col.find(**params).skip(config[MongoQueryConfig.skip])
            else:
                records_crusor = col.find(**params)  # .limit(10000).sort([("$natural",-1)]),

        return records_crusor

    def get_data_from_mongo(self, config, save_result_dic=None,is_silk=False):
        '''
        模板方法（固定流程）： 从mongo中查出数据，然后遍历处理
        @param config: config = {
            'col_name': 集合名称 str
            'filter': 筛选条件 dic
            'projection': 投影 list of str
        @param save_result_dic: 存放统计数据的dict
        @return:result_dic
        '''
        # 0 mobile 手机 ； 1 phone 固话 ；2 email
        if is_silk:
            records_crusor = self.get_records_crusor(config,is_silk=is_silk)
        else:
            records_crusor = self.get_records_crusor(config)
        # error_count = 0 # 出错次数
        for index, record_dic in enumerate(records_crusor):
            self.handle_index_log(index, save_result_dic)
            try:
                save_result_dic = self.handle_one_mongo_data(record_dic, save_result_dic)
            except Exception as e:
                self.write_error_f.write('params： {}\nand\n{}\ncause error:\n{}\n\n'.format(record_dic, save_result_dic,
                                                                                            traceback.format_exc()))
                self.write_error_f.flush()
                raise Exception('错误信息 {} 已记录在error文件'.format(repr(e)))

                # self.write_error_f.write('error_count:{}'.format(error_count))
                # if error_count%1000:
                #     self.write_error_f.write('params： {}\nand\n{}\ncause error:\n{}\n\n'.format(record_dic,save_result_dic,traceback.format_exc()))
                #     self.write_error_f.flush()
                # error_count += 1
        else:
            self.log('get_data_from_mongo over ')
            self.save_tj_info(save_result_dic)

        return save_result_dic

    def handle_one_mongo_data(self, record_dic, save_result_dic=None):
        '''
        对一条查询结果进行处理，交由子类实现
        用途：导出符合条件的，或者进行统计
        @param record_dic: 数据库一条记录  dict
        @param save_result_dic: 存放统计结果  dict
        @return: save_result_dic  dict
        '''
        pass

    def run(self):
        '''
        主逻辑
        @return:None
        '''
        pass

    def main(self):
        '''
        运行入口
        @return:None
        '''
        self.run()
        self.close_file()
